﻿/* Autor: Achim Schnell */

using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace AppFx.ComponentModel.Descriptive
{
	/// <summary>
	/// Diese Klasse stellt PropertyDescriptors zu den ihr übergebenen
	/// Typen zur Verfügung, welche kompilierte Accessoren nutzen - und
	/// damit für eine schnellere Zugriffsmöglichkeit auf unbekannte Eigenschaften
	/// ermöglichen.
	/// </summary>
	/// <remarks>
	/// Bitte beachten:
	/// Jeder Aufruf einer Methode nutzt hier Reflection >> Overhead!
	/// Die Rückgaben sollten (soweit möglich / sinnvoll) gecached werden.
	/// </remarks>
	public class CompiledTypeDescriptionProvider : ITypeDescriptionProvider
	{
		/// <summary>
		/// Gibt die Liste von Delegaten zurück, welche die Ausgabemenge
		/// aller GetDescriptors-Methoden einschränken kann.
		/// </summary>
		public IList<Func<PropertyDescriptor, bool>> Exclusions { get; set; }

		/// <summary>
		/// Erstellt eine neue Instanz der <see cref="CompiledTypeDescriptionProvider"/>-Klasse.
		/// </summary>
		public CompiledTypeDescriptionProvider()
		{
			Exclusions = new List<Func<PropertyDescriptor, bool>>();
		}

		/// <summary>
		/// Erstellt eine Liste mit PropertyDescriptors zum übergebenen Typ.
		/// </summary>
		/// <param name="instance">Objekt, für dessen Typ die PropertyDescriptors erstellt werden sollen.</param>
		/// <returns>Eine Liste aller erstellten PropertyDescriptors</returns>
		/// <exception cref="T:System.ArgumentNullException">Wird geworfen, wenn der Parameter <paramref name="instance"/> NULL ist.</exception>
		/// <remarks>
		/// Diese Methode stellt PropertyDescriptors zur Verfügung, welche vorkompilierte
		/// Accessoren nutzen, um schnelleren Zugriff (im Vergleich zu purer Reflection)
		/// zu ermöglichen.
		/// 
		/// Berücksichtigt alle Delegaten in der Liste 'Exclusions'.
		/// </remarks>
		public virtual IList<PropertyDescriptor> GetDescriptors(object instance)
		{
			if (instance == null)
				throw new ArgumentNullException("instance");
			return GetDescriptors(instance.GetType());
		}

		/// <summary>
		/// Erstellt eine Liste mit PropertyDescriptors zum übergebenen Typ.
		/// </summary>
		/// <param name="type">Typ, für den die PropertyDescriptors erstellt werden sollen.</param>
		/// <returns>Eine Liste aller erstellten PropertyDescriptors</returns>
		/// <exception cref="T:System.ArgumentNullException">Wird geworfen, wenn der Parameter <paramref name="type"/> NULL ist.</exception>
		/// <remarks>
		/// Diese Methode stellt PropertyDescriptors zur Verfügung, welche vorkompilierte
		/// Accessoren nutzen, um schnelleren Zugriff (im Vergleich zu purer Reflection)
		/// zu ermöglichen.
		/// 
		/// Berücksichtigt alle Delegaten in der Liste 'Exclusions'.
		/// </remarks>
		public virtual IList<PropertyDescriptor> GetDescriptors(Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type");

			List<PropertyDescriptor> descriptors = new List<PropertyDescriptor>();
			PropertyInfo[] pis = type.GetProperties();

			if (pis == null || pis.Length == 0)
				return descriptors;

			for (int i = 0; i < pis.Length; i++)
			{
				descriptors.Add(new CompiledPropertyDescriptor(type, pis[i]));
			}

			foreach (var exclusion in Exclusions)
			{
				IEnumerable<PropertyDescriptor> excludedDescriptors = descriptors.Where(exclusion);
				descriptors = descriptors.Except(excludedDescriptors).ToList();
			}

			return descriptors;
		}

		/// <summary>
		/// Erstellt eine Liste mit PropertyDescriptors zum übergebenen Typ.
		/// </summary>
		/// <param name="instance">Objekt, für dessen Typ die PropertyDescriptors erstellt werden sollen.</param>
		/// <param name="exclude">Der Delegat um PropertyDescriptors auszuschließen.</param>
		/// <returns>Eine Liste aller erstellten PropertyDescriptors</returns>
		/// <exception cref="T:System.ArgumentNullException">Wird geworfen, wenn der Parameter <paramref name="type"/> NULL ist.</exception>
		/// <remarks>
		/// Diese Methode stellt PropertyDescriptors zur Verfügung, welche vorkompilierte
		/// Accessoren nutzen, um schnelleren Zugriff (im Vergleich zu purer Reflection)
		/// zu ermöglichen.
		/// 
		/// Berücksichtigt alle Delegaten in der Liste 'Exclusions'.
		/// </remarks>
		public virtual IList<PropertyDescriptor> GetDescriptors(object instance, Func<PropertyDescriptor, bool> exclude)
		{
			if (instance == null)
				throw new ArgumentNullException("instance");
			return GetDescriptors(instance.GetType(), exclude);
		}

		/// <summary>
		/// Erstellt eine Liste mit PropertyDescriptors zum übergebenen Typ.
		/// </summary>
		/// <param name="type">Typ, für den die PropertyDescriptors erstellt werden sollen.</param>
		/// <param name="exclude">Der Delegat um PropertyDescriptors auszuschließen.</param>
		/// <returns>Eine Liste aller erstellten PropertyDescriptors</returns>
		/// <exception cref="T:System.ArgumentNullException">Wird geworfen, wenn der Parameter <paramref name="type"/> NULL ist.</exception>
		/// <remarks>
		/// Diese Methode stellt PropertyDescriptors zur Verfügung, welche vorkompilierte
		/// Accessoren nutzen, um schnelleren Zugriff (im Vergleich zu purer Reflection)
		/// zu ermöglichen.
		/// 
		/// Berücksichtigt alle Delegaten in der Liste 'Exclusions'.
		/// </remarks>
		public virtual IList<PropertyDescriptor> GetDescriptors(Type type, Func<PropertyDescriptor, bool> exclude)
		{
			if (type == null)
				throw new ArgumentNullException("type");
			IList<PropertyDescriptor> descriptors = GetDescriptors(type);
			IList<PropertyDescriptor> excludedDescriptors = descriptors.Where(exclude).ToList();
			return GetDescriptors(type).Except(excludedDescriptors).ToList();
		}
	}
}
